<?php

namespace App\Service;

use App\Cache\RedisKey;
use App\Constants\ErrorCode;
use App\Constants\InviteVideoCode;
use App\Exception\ApiException;
use App\Helpers\ConfigUtil;
use App\Helpers\RedisUtil;
use App\Model\CallBase;
use App\Model\CallHangup;
use App\Model\CloseFriend;
use App\Model\DiscountVideoUsers;
use App\Model\Setting\SettingAppHidden;
use App\Model\User;
// use App\Model\Message;
use App\Model\UserBlacklist;
use App\Model\UserCoin;
use App\Model\UserFreeCall;
use App\Model\UserProfile;
use App\Model\UserRecommand;
use App\Model\VideoCall;
use App\Model\VideoCallAuditConfig;
use App\Model\VoiceCall;
use App\Service\BaseService;
use Hyperf\DbConnection\Db;
use Hyperf\Cache\Annotation\Cacheable;
/**
 * 语音拨打-父类
 */
class CallBaseService extends BaseService
{
    use \Hyperf\Di\Aop\ProxyTrait;
    use \Hyperf\Di\Aop\PropertyHandlerTrait;
    function __construct(\Hyperf\AsyncQueue\Driver\DriverFactory $driverFactory)
    {
        if (method_exists(parent::class, '__construct')) {
            parent::__construct(...func_get_args());
        }
        $this->__handlePropertyHandler(__CLASS__);
    }
    /**
     * 业务锁
     * @var string
     */
    protected $callLock;
    /**
     * 拨打电话后处理
     */
    function callHook($toUserId = 0, $fromUserId = 0, $callType = 0)
    {
        // if ($callType == VideoCall::CALL_TYPE) return false;
        log_info(__CLASS__ . "==Host:{$toUserId}==User:{$fromUserId}");
        //系统通知
        // $fromUser = UserProfile::getProfile($fromUserId);
        // Message::pushSystem($toUserId, "{$fromUser['nick_name']} 刚刚呼叫你了哦~");
    }
    /**
     * 检测通话权限
     */
    function checkCall($userId, $hostUserId, $callType = VoiceCall::CALL_TYPE, $checkCoin = true, $callFrom = -1)
    {
        //获取对方和自己资料信息
        $profile = UserProfile::getProfile($hostUserId);
        $validAppIds = explode(',', env('CHAT_NO_LIMIT_APPID', 6));
        if ((!$profile || $profile->avatar_vrfy != 1) && !in_array($this->appId, $validAppIds)) {
            return $this->setError(ErrorCode::PARAMETER_ERROR, '非认证状态，暂不可呼叫～');
        }
        //拉黑关系
        $checkBlack = UserBlacklist::check($hostUserId, $userId);
        if ($checkBlack) {
            return $this->setError(ErrorCode::CHAT_BLACK_LMT, '暂无法发起通话~');
        }
        //对方是否允许接听，或者对方正在通话
        $permitField = $callType == VoiceCall::CALL_TYPE ? 'call_permit' : 'video_permit';
        if ($profile->{$permitField} == 0) {
            return $this->setError(ErrorCode::HOST_BUSY, '对方忙或手机不在身边，请稍后再试');
        }
        $callCls = $callType == VoiceCall::CALL_TYPE ? VoiceCall::class : VideoCall::class;
        //对方是否在打语音电话
        if (VoiceCall::checkUserChatting($hostUserId) || VideoCall::checkUserChatting($hostUserId)) {
            return $this->setError(ErrorCode::HOST_BUSY, '对方忙或手机不在身边，请稍后再试');
        }
        // 判断对方用户状态
        $user = User::getUser($hostUserId);
        if ($user && $user->status != User::STATUS_OK) {
            return $this->setError(ErrorCode::DEFAULT_ERROR, '对方暂无法接听');
        }
        if ($callFrom == CallBase::FROM_DISCOUNT_CALL) {
            $coinSetting = DiscountVideoUsers::DISCOUNT_COIN;
        } else {
            $coinSetting = $callType == VoiceCall::CALL_TYPE ? $profile->coin_setting : $profile->video_coin_setting;
        }
        //检查余额
        if ($checkCoin && $callFrom != CallBase::FROM_INVITE_CALL) {
            //个人虚拟币账户信息
            $userCoin = UserCoin::getUserInfo($userId);
            //账户不够
            if (!$userCoin || $userCoin['total_coin'] < $coinSetting) {
                return $this->setError(ErrorCode::COIN_NOT_ENOUGH, '你的余额不足，请充值后再试~');
            }
        }
        return ['ret' => 0, 'hostCoinSetting' => $coinSetting, 'hostProfile' => $profile];
    }
    /**
     * 通话挂断检测
     */
    function checkHangup($userId = 0, $chatUserId = 0, $callType = VoiceCall::CALL_TYPE)
    {
        //对方ID为0
        if ($chatUserId == '0') {
            return ['ret' => 0, 'return' => 1];
        }
        $callCls = $callType == VoiceCall::CALL_TYPE ? VoiceCall::class : VideoCall::class;
        //根据频道ID查记录
        $record = $callCls::checkRecord($userId, $chatUserId);
        //非通话中的状态不处理
        if (!$record) {
            return $this->setError(ErrorCode::PARAMETER_ERROR, '通话记录不存在');
        }
        $ret = ['ret' => 0, 'return' => 0, 'record' => $record];
        //已经挂断状态
        // if ($record->status == CallBase::STATUS_OK) {
        //     $ret['return'] = 1;
        //     return $ret;
        // }
        //非挂断，其他状态
        if ($record->status != CallBase::STATUS_ING) {
            // return $this->setError(ErrorCode::PARAMETER_ERROR, '该通话已挂断！');
            $ret['return'] = 1;
            return $ret;
        }
        //同步锁
        $this->callLock = $callType == VideoCall::CALL_TYPE ? RedisKey::operateVideoCallLock($record->id) : RedisKey::operateVoiceCallLock($record->id);
        if (!RedisUtil::addLock($this->callLock, 10)) {
            log_info(__CLASS__ . "==正在挂断该通话={$callCls}==CallId:{$record->id}");
            $ret['return'] = 1;
        }
        return $ret;
    }
    /**
     * 天天福利
     */
    function dailyFreeCall($userId, $callCls)
    {
        //        $today = date('Y-m-d');
        $callType = $callCls::CALL_TYPE;
        //是否主播
        //        $isHost = UserProfile::checkIsHost($userId);
        $isHost = 1;
        //        $checkWhiteList = [206];
        //        //是否已经用完免费通话券
        //        $freeCallRecord = UserFreeCall::checkValid($userId, $today);
        //        if (!$isHost && !$freeCallRecord && !in_array($userId, $checkWhiteList)) {
        //            return $this->setError(ErrorCode::FREE_CALL_USED, "今日福利已用完，请明日再来领取哦！");
        //        }
        $selfProfile = UserProfile::getProfile($userId);
        //        return $this->fromHostCall($userId, $matchUserId, VideoCall::class, $selfProfile);
        $coinSetting = $callType == VoiceCall::CALL_TYPE ? $selfProfile->coin_setting : $selfProfile->video_coin_setting;
        //对方账户信息
        #获取指定用户速配用户
        $matchUserId = $this->getMatchUser($userId, $callType, $isHost, $coinSetting);
        if (!$matchUserId) {
            return $this->setError(ErrorCode::NO_VALID_HOST, '当前用户均在忙碌中，请稍后再试~');
        }
        $callFrom = CallBase::FROM_DAILY_FREE;
        $callMark = 2;
        if ($callType == VideoCall::CALL_TYPE && VideoCall::getInviteVideoTotal($matchUserId) < InviteVideoCode::TOTAL_MAX_ANSWER_NUM) {
            $callFrom = VideoCall::FROM_INVITE_CALL;
            $callMark = 3;
            //            list($userId,$hostId) = array($hostId,$userId);
            //                $freeCall = 1;
        }
        if ($callFrom != VideoCall::FROM_INVITE_CALL) {
            $userCoin = UserCoin::getUserInfo($matchUserId);
            $coinLack = !$userCoin || $userCoin['total_coin'] < $coinSetting;
            //账户不够
            if ($coinLack) {
                throw new ApiException('对方账户余额不足，请让对方充值后再试~', ErrorCode::PARAMETER_ERROR);
                //            return $this->setError(ErrorCode::PARAMETER_ERROR, '对方账户余额不足，请让对方充值后再试~');
            }
        }
        //修改以前无效状态
        $callCls::finishOldCall($userId);
        //添加记录
        //        $coinSetting = ConfigUtil::getDailyFreeCoin($callType, $isHost);
        $callCls::addRecord($userId, $matchUserId, $callFrom, $coinSetting, $callMark);
        //呼叫回调
        // $this->callHook($hostId, $userId);
        log_info(__METHOD__ . "=={$userId}==CallType:{$callType}==MatchId:{$matchUserId}");
        // 鉴黄配置
        $config = (new VideoCallAuditConfig())->getConfig($userId);
        return ['user_info' => UserProfile::buildHostItem(UserProfile::getProfile($matchUserId)), 'show_avatars' => UserProfile::getBlurAvatars(20), 'support_face' => User::checkFaceSupport($matchUserId), 'jHConfig' => $config];
    }
    /**
     * 主播免费福利通话呼叫
     */
    function getMatchUser($userId = 0, $callType, $isHost = false, $coinSetting = 0)
    {
        //主播
        if ($isHost) {
            //获取指定的用户
            //            $matchId = $this->getUserForMatch($userId, 1, $callType);
            return $matchId = $this->getUserForMatchV1($userId, 1, $callType, $coinSetting);
            //            return !empty($matchId) ? $matchId : $this->getUserForMatchV1($userId, -1, $callType,$coinSetting) ;
        } else {
            //获取指定的主播
            $hostId = $this->getHostsForMatch($userId, 1, $callType);
            return $hostId ?: $this->getHostsForMatch($userId, -1, $callType);
        }
    }
    /**
     * 获取匹配的主播ID
     */
    function getHostsForMatch($userId = 0, $online = -1, $callType = VoiceCall::CALL_TYPE)
    {
        //推荐声优
        $hostIds = UserProfile::getRecHosts(0, $online, $callType);
        //测试白名单
        if ($confHostIds = ConfigUtil::getCallWhitelist('free_daily')) {
            $hostIds = array_intersect($hostIds, $confHostIds);
        }
        //排除正在通话的
        $callingHosts = array_merge(VideoCall::getCallingHostIds(), VoiceCall::getCallingHostIds());
        //排除最近挂断用户的
        $hangupHosts = CallHangup::latestRecords($userId, 1);
        //排除
        $hostIds = array_values(array_diff($hostIds, $callingHosts, $hangupHosts, [$userId]));
        log_user_result($userId, "getHostForMatch==Calling:" . json_encode($callingHosts) . "==Hangup:" . json_encode($hangupHosts) . "==Hosts:" . json_encode($hostIds));
        if (empty($hostIds)) {
            return 0;
        }
        return $hostIds[array_rand($hostIds)];
    }
    /**
     * 获取匹配的普通用户ID
     */
    function getUserForMatch($userId = 0, $online = -1, $callType = VoiceCall::CALL_TYPE)
    {
        //测试白名单
        if ($confHostIds = ConfigUtil::getCallWhitelist('free_daily_host')) {
            $matchIds = $confHostIds;
        } else {
            //在线未充值的用户
            $matchIds = $this->getUnPayedUsers($online, $userId);
            //排除正在通话的
            $callingHosts = CallBase::getCallingUsers();
            //排除N分钟内挂断用户的
            // $offMinutes = 3;
            // $hangupHosts = env('CALL_HANGUP_EXCLUDE_WAY', 1) == 1
            //     ? CallHangup::latestRecords($userId, 60)
            //     : CallHangup::getTodayHangupUsers(5);
            $hangupUsers = CallHangup::getTodayHangupUsers(60);
            $hangupUserInfo = array_count_values($hangupUsers);
            $hangupUserInfo = array_filter($hangupUserInfo, function ($v) {
                return $v >= 2;
            });
            $hangupUsers = array_keys($hangupUserInfo);
            //排除已经被主播成功呼叫3次以上的
            $calledLmtUsers = CallBase::getCalledByTimes(3, '2022-06-01');
            //排除
            $matchIds = array_values(array_diff($matchIds, $callingHosts, $hangupUsers, $calledLmtUsers, [$userId]));
            log_user_result($userId, "getUserForMatch==Calling:" . count($callingHosts) . "==Hangup:" . count($hangupUsers) . "==CalledLmt:" . count($calledLmtUsers) . "==Total:" . count($matchIds));
        }
        if (empty($matchIds)) {
            return 0;
        }
        return $matchIds[array_rand($matchIds)];
    }
    /**
     * 获取在线用户
     */
    function getUserForMatchV1($userId = 0, $online = -1, $callType = VoiceCall::CALL_TYPE, $coinSetting = 0)
    {
        //测试白名单
        if ($confHostIds = ConfigUtil::getCallWhitelist('free_daily_host')) {
            $matchIds = $confHostIds;
        } else {
            //在线已完善资料的用户
            $onelines = $matchIds = $this->getOnlineUsers($online, $userId);
            $coinUids = UserCoin::getByuidsCoin($matchIds, $coinSetting);
            $onlineCoin = array_intersect($onelines, $coinUids);
            //            $matchIds = array_unique(array_merge($onlineMatchIds,$onelines));
            $noFreeUserId = VideoCall::where('from', VideoCall::FROM_INVITE_CALL)->whereIn('status', [CallBase::STATUS_OK, CallBase::STATUS_ING])->pluck('user_id')->toArray();
            $freeUserId = array_diff($onelines, $noFreeUserId);
            $matchIds = array_unique(array_merge($onlineCoin, $freeUserId));
            #排除白名单
            $speedDatingUids = config('busi.system_account');
            //排除拉黑
            $userBlackIds = UserBlacklist::getList($userId, 'uid');
            //排除正在通话的
            $callingHosts = CallBase::getCallingUsers();
            //排除当日已接通过速配的用户ID
            $time = date('Y-m-d');
            $calledLmtUsers = CallBase::getCalledByTimes(10, $time);
            //排除当日和自己拨打过的用户
            $calledRefuseUser = CallBase::getCalledRefuse($userId, 1, $time);
            //排除N分钟内挂断用户的
            $hangupUsers = CallHangup::getTodayHangupUsers(20);
            $hangupUserInfo = array_count_values($hangupUsers);
            $hangupUserInfo = array_filter($hangupUserInfo, function ($v) {
                return $v >= 2;
            });
            $hangupUsers = array_keys($hangupUserInfo);
            //排除有亲密的用户
            $friendUsers = [];
            //            $friendUsers = CloseFriend::getFrdList($userId);
            $matchIds = array_values(array_diff($matchIds, $callingHosts, $calledLmtUsers, $hangupUsers, [$userId], $speedDatingUids, $calledRefuseUser, $userBlackIds, $friendUsers));
            log_user_result($userId, "getUserForMatch==Calling:" . count($callingHosts) . "==Hangup:" . count($calledLmtUsers) . "==coinUids:" . count($calledLmtUsers) . "==Total:" . count($matchIds));
        }
        if (empty($matchIds)) {
            return 0;
        }
        return $matchIds[array_rand($matchIds)];
    }
    /**
     * 在线未充值用户
     */
    function getUnPayedUsers($online = -1, $userId = 0)
    {
        //在线筛选
        $onlineCond = $online > -1 ? "AND online={$online}" : "";
        // $updateTime = date('Y-m-d H:i:s', time() - 2 * 3600);
        //注册时间在一天内
        $updateTimeCond = '';
        //check_dev_env() ? "" : "AND updated_at>'{$updateTime}'";
        //未充值
        $unPayCond = "AND (SELECT count(1) FROM t_pay_order WHERE t_user_profile.user_id=t_pay_order.user_id AND status=1)=0";
        $rawCond = "gender=1 {$onlineCond} {$updateTimeCond} {$unPayCond} ORDER BY updated_at DESC limit 500";
        log_user_result($userId, $rawCond);
        //未充值
        $userIds = Db::table('user_profile')->whereRaw($rawCond)->pluck('user_id')->toArray();
        //额外屏蔽
        if (!config('busi.online_tip_enable')) {
            $settings = SettingAppHidden::getSetting(SettingAppHidden::FUNC_PUSH);
            if (!empty($settings)) {
                foreach ($settings as $setting) {
                    //                    $excludeUsers = UserRecommand::getExcludeUsers($setting['channel'], $setting['version']);
                    $excludeUsers = UserRecommand::getExcludeUsersCity($setting['channel'], $setting['version'], '', '', $setting['hidden_city']);
                    $userIds = array_diff($userIds, $excludeUsers);
                    log_user_result($userId, "ExcludeChannel:" . json_encode($setting['channel']) . "==ExcludeVersion:{$setting['version']}==Count:" . count($excludeUsers));
                }
            }
        }
        return $userIds;
    }
    /**
     * 获取在线用户
     *
     * @Cacheable(prefix="get_online_user", ttl=60, listener="get_online_user")
     *
     * @param int $online
     * @param int $userId
     * @return array
     */
    function getOnlineUsers($online = -1, $userId = 0)
    {
        $__function__ = __FUNCTION__;
        $__method__ = __METHOD__;
        return self::__proxyCall(__CLASS__, __FUNCTION__, self::__getParamsMap(__CLASS__, __FUNCTION__, func_get_args()), function ($online = -1, $userId = 0) use($__function__, $__method__) {
            #获取屏蔽资料
            //        $setting = SettingAppHidden::getHiddenWhere();
            $userIds = Db::table('user_profile')->join('user', 'user.id', '=', 'user_profile.user_id')->where(function ($query) use($online) {
                //在线筛选
                $onlineCond = $online > -1 ? " AND online={$online}" : '';
                //注册时间在一天内
                $updateTimeCond = '';
                //未充值
                //                $unPayCond = "AND (SELECT count(1) FROM t_pay_order WHERE t_user_profile.user_id=t_pay_order.user_id AND status=1)=0";
                $rawCond = "gender=1 and birthday <> '' {$onlineCond} {$updateTimeCond} ";
                $query->whereRaw($rawCond);
            })->where('user.status', 0);
            //        $userIds = (new SettingAppHidden())->getAppHiddenSubQuery($userIds);
            $userIds = $userIds->orderBy('user_profile.updated_at', 'desc')->limit(500)->pluck('user_id')->toArray();
            $excludeUsers = SettingAppHidden::getExcludeUsers();
            $userIds = array_diff($userIds, $excludeUsers);
            return $userIds;
            //        $payUserIds = Db::table('user_coin')->pluck('user_id')->toArray() ?? [];
            //
            //        $payCoinIds = Db::table('user_coin')->where('recharge_coin','<=',10)->pluck('user_id')->toArray() ?? [];
            //
            //        return array_merge(array_diff($userIds,$payUserIds),array_intersect($userIds,$payCoinIds)) ?? [];
        });
    }
    /**
     * 主播呼叫
     */
    function fromHostCall($userId, $chatUserId, $callCls, $selfProfile = null)
    {
        //检测拨打权限
        $checkResult = $this->checkHostCall($userId, $chatUserId, $callCls::CALL_TYPE, $selfProfile);
        if ($checkResult['ret'] != 0) {
            return $checkResult;
        }
        //修改以前无效状态
        $callCls::finishOldCall($userId);
        //加记录
        $ret = $callCls::addRecord($chatUserId, $userId, CallBase::FROM_HOST_CALL, $checkResult['coinSetting']);
        //呼叫回调
        // $this->callHook($chatUserId, $userId, $callCls::CALL_TYPE);
        log_user_result($userId, "ChatUserId:{$chatUserId}==" . json_encode($ret));
        // 鉴黄配置
        $config = (new VideoCallAuditConfig())->getConfig($userId);
        return ['permit' => 1, 'user_info' => UserProfile::buildHostItem($checkResult['chatProfile']), 'coin_lack' => $checkResult['coinLack'], 'support_face' => User::checkFaceSupport($chatUserId), 'jHConfig' => $config];
    }
    /**
     * 主播呼叫检测
     */
    private function checkHostCall($userId, $chatUserId, $callType, $selfProfile = null)
    {
        //拉黑关系
        $checkBlack = UserBlacklist::check($chatUserId, $userId);
        if ($checkBlack) {
            return $this->setError(ErrorCode::CHAT_BLACK_LMT, '暂无法发起通话~');
        }
        //检查亲密值
        $checkKey = $callType == VoiceCall::CALL_TYPE ? 'voice_call' : 'video_call';
        $callLmt = CloseFriend::checkLimit($userId, $chatUserId, $checkKey);
        if ($callLmt) {
            return $this->setError(ErrorCode::CLOSE_VALUE_LMT, '你们亲密值还未到等级，暂不可呼叫～');
        }
        #return  $this->setError(ErrorCode::OP_NOT_VALID, $chatUserId);
        #$a = VideoCall::checkUserChatting($chatUserId, true);
        ##return  $this->setError(ErrorCode::OP_NOT_VALID, $a);
        #$b = VoiceCall::checkUserChatting($chatUserId, true);
        #return  $this->setError(ErrorCode::OP_NOT_VALID, $b);
        //对方是否在打电话
        if (VideoCall::checkUserChatting($chatUserId, true) || VoiceCall::checkUserChatting($chatUserId, true)) {
            return $this->setError(ErrorCode::OP_NOT_VALID, '对方当前正忙或在通话中，暂无法呼叫～');
        }
        //是否在打电话
        if (VideoCall::checkUserChatting($userId) || VoiceCall::checkUserChatting($userId)) {
            return $this->setError(ErrorCode::OP_NOT_VALID, '你当前正在通话中，无法呼叫～');
        }
        $coinSetting = $callType == VoiceCall::CALL_TYPE ? $selfProfile->coin_setting : $selfProfile->video_coin_setting;
        //对方账户信息
        $userCoin = UserCoin::getUserInfo($chatUserId);
        $coinLack = !$userCoin || $userCoin['total_coin'] < $coinSetting;
        //账户不够
        if ($coinLack && !User::checkUserValidVersion($chatUserId, [1 => '1.2.2', 2 => '1.2.5'])) {
            return $this->setError(ErrorCode::PARAMETER_ERROR, '对方账户余额不足，请让对方充值后再试~');
        }
        //对方资料
        $chatProfile = UserProfile::getProfile($chatUserId);
        return ['ret' => 0, 'coinSetting' => $coinSetting, 'chatProfile' => $chatProfile, 'coinLack' => $coinLack ? 1 : 0];
    }
}